/*
 * Copyright (C) 2014 Sony Computer Entertainment Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "SigreCommandBuffer.h"

#if USE(ACCELERATED_COMPOSITING) && USE(ACAGI)

#include "AcagiConfig.h"
#include "FloatRect.h"
#include "NotImplemented.h"
#include <stdio.h>

#define DBG_TRACE printf("%s %d\n", __PRETTY_FUNCTION__, __LINE__)

#if DEBUG_TILEDBACKEND_V2
static const WebCore::FloatRect MemoryBarArea(30, 480, 800, 20);
#endif

// Converters
namespace tilebackend {
IntRect fromCore(const WebCore::IntRect &o)
{
    IntRect r;
    r.x = o.x();
    r.y = o.y();
    r.w = o.width();
    r.h = o.height();
    return r;
}

FloatRect fromCore(const WebCore::FloatRect &o)
{
    FloatRect r;
    r.x = o.x();
    r.y = o.y();
    r.w = o.width();
    r.h = o.height();
    return r;
}

TransformationMatrix fromCore(const WebCore::TransformationMatrix &o)
{
    float floatMatrix4[16];
    o.toColumnMajorFloatArray(floatMatrix4);
    TransformationMatrix r;
    for (int i = 0; i < 4; i++)
        for (int j = 0; j < 4; j++)
            r.m[i][j] = floatMatrix4[i + j * 4];

    return r;
}

}

namespace WebCore {

PassOwnPtr<SigreCommandBuffer> SigreCommandBuffer::create(Allocator* allocator, PlatformGraphics* gfx)
{
    ASSERT(allocator);
    ASSERT(gfx);
    SigreCommandBuffer* context = new SigreCommandBuffer(allocator, gfx);
    return adoptPtr(context);
}

PassRefPtr<SigreTexture> SigreCommandBuffer::createTexture(IntSize textureSize)
{
    return SigreTexture::create(m_allocator, textureSize);
}

SigreCommandBuffer::SigreCommandBuffer(Allocator* allocator, PlatformGraphics* gfx)
    : m_allocator(allocator)
    , m_gfx(gfx)
{
}

SigreCommandBuffer::~SigreCommandBuffer()
{
}

void SigreCommandBuffer::clearCommands()
{
    DBG_TRACE;
    m_back.clear();
    m_frontLocker.lock();
    m_front.clear();
    m_frontLocker.unlock();
}

struct SigreCommand {
    enum Type {
        BeginPainting,
        EndPainting,
        DrawTexture,
        DrawSolidColor,
        Scissor
    };
    SigreCommand(Type type) : type(type) { }
    virtual ~SigreCommand() { }

    const Type type;
};

struct SigreCommandBeginPainting : public SigreCommand {
    SigreCommandBeginPainting() : SigreCommand(BeginPainting) { }
};

struct SigreCommandEndPainting : public SigreCommand {
    SigreCommandEndPainting() : SigreCommand(EndPainting) { }
};

struct SigreCommandDrawTexture : public SigreCommand {
    SigreCommandDrawTexture(PassRefPtr<SigreTexture> texture, const FloatRect& target, const TransformationMatrix& modelViewMatrix, float opacity)
        : SigreCommand(DrawTexture)
        , texture(texture)
        , target(target)
        , modelViewMatrix(modelViewMatrix)
        , opacity(opacity)
    { }

    RefPtr<SigreTexture> texture;
    FloatRect target;
    TransformationMatrix modelViewMatrix;
    float opacity;
};

struct SigreCommandDrawSolidColor : public SigreCommand {
    SigreCommandDrawSolidColor(const FloatRect& target, const TransformationMatrix& modelViewMatrix, const Color& color)
        : SigreCommand(DrawSolidColor)
        , target(target)
        , modelViewMatrix(modelViewMatrix)
        , color(color)
    { }

    FloatRect target;
    TransformationMatrix modelViewMatrix;
    Color color;
};

struct SigreCommandScissor : public SigreCommand {
    SigreCommandScissor(const FloatRect& scissorBox, bool enable)
        : SigreCommand(Scissor)
        , scissorBox(scissorBox)
        , enable(enable)
    { }

    FloatRect scissorBox;
    bool enable;
};

// Drawing API
void SigreCommandBuffer::beginPainting()
{
    m_back = adoptPtr(new CommandList);

    SigreCommandBeginPainting* command = new SigreCommandBeginPainting;
    m_back->append(adoptPtr(command));
}

void SigreCommandBuffer::endPainting()
{
    SigreCommandEndPainting* command = new SigreCommandEndPainting;
    m_back->append(adoptPtr(command));

    // Move back command buffer to front
    m_frontLocker.lock();
    m_front = m_back.release();
    m_frontLocker.unlock();
}

void SigreCommandBuffer::drawBorder(const Color&, float borderWidth, const FloatRect&, const TransformationMatrix&)
{
}

void SigreCommandBuffer::drawNumber(int number, const Color&, const FloatPoint&, const TransformationMatrix&)
{
}

void SigreCommandBuffer::drawTexture(PassRefPtr<SigreTexture> texture, const FloatRect& target, const TransformationMatrix& modelViewMatrix, float opacity)
{
    SigreCommandDrawTexture* command = new SigreCommandDrawTexture(texture, target, modelViewMatrix, opacity);
    m_back->append(adoptPtr(command));
}

void SigreCommandBuffer::drawSolidColor(const FloatRect& target, const TransformationMatrix& modelViewMatrix, const Color& color)
{
    SigreCommandDrawSolidColor* command = new SigreCommandDrawSolidColor(target, modelViewMatrix, color);
    m_back->append(adoptPtr(command));
}

void SigreCommandBuffer::bindSurface(PassRefPtr<SigreTexture> surface)
{
}

void SigreCommandBuffer::scissor(const FloatRect& scissorBox, bool enable)
{
    SigreCommandScissor* command = new SigreCommandScissor(scissorBox, enable);
    m_back->append(adoptPtr(command));
}

//
void SigreCommandBuffer::patchAndSubmitDrawCommands(const IntRect& clipRect, TargetSurface* targetSurface)
{
    if (clipRect.isEmpty())
        return;

    float scaleX = (float)targetSurface->width / clipRect.width();
    float scaleY = (float)targetSurface->height / clipRect.height();

    TransformationMatrix transformation;
    
#if DBG_SCALE_DOWN
    transformation.translate(targetSurface->width * 0.25, targetSurface->height * 0.25);
    transformation.scale(0.5);
#endif
    
    transformation.scale3d(scaleX, scaleY, 1);
    transformation.translate(-clipRect.x(), -clipRect.y());

    MutexLocker locker(m_frontLocker);
    if (!m_front)
        return;
    size_t commandNum = m_front->size();
    if (!commandNum)
        return;


    for (size_t i = 0; i < commandNum; i++) {
        SigreCommand* command = m_front->at(i).get();
        switch (command->type) {
        case SigreCommand::BeginPainting:
            {
                m_gfx->beginPaint(targetSurface);

                // Clear surface for debugging.
                tilebackend::FloatRect target;
                target.x = 0;
                target.y = 0;
                target.w = targetSurface->width;
                target.h = targetSurface->height;
                tilebackend::TransformationMatrix matrix = tilebackend::fromCore(TransformationMatrix());
                tilebackend::Color color;
                color.rgba[0] = 1;
                color.rgba[1] = 1;
                color.rgba[2] = 1;
                color.rgba[3] = 1;
                m_gfx->drawSolidColor(target, matrix, color);
            }
            break;
        case SigreCommand::EndPainting:
            {

#if DBG_SCALE_DOWN
                {
                    tilebackend::FloatRect target;
                    target.x = targetSurface->width * 0.25;
                    target.y = targetSurface->height * 0.25;
                    target.w = targetSurface->width * 0.5;
                    target.h = targetSurface->height * 0.5;
                    tilebackend::TransformationMatrix matrix = tilebackend::fromCore(TransformationMatrix());
                    tilebackend::Color color;
                    color.rgba[0] = 1;
                    color.rgba[1] = 1;
                    color.rgba[2] = 1;
                    color.rgba[3] = 1;
                    m_gfx->drawBorder(target, matrix, color);
                    }
#endif

#if DEBUG_TILEDBACKEND_V2 && DBG_SHOW_MEMORY_USAGE
                // Show memory usage
                tilebackend::FloatRect target;
                target.x = MemoryBarArea.x();
                target.y = MemoryBarArea.y();
                target.w = MemoryBarArea.width();
                target.h = MemoryBarArea.height();
                tilebackend::TransformationMatrix matrix = tilebackend::fromCore(TransformationMatrix());
                tilebackend::Color color;
                color.rgba[0] = 0;
                color.rgba[1] = 0;
                color.rgba[2] = 0;
                color.rgba[3] = 0.5;
                m_gfx->drawSolidColor(target, matrix, color);
                m_allocator->reportMemoryChunkInfo(memoryChunkInfoCallback, this);
#endif
                m_gfx->endPaint();
            }
            break;
        case SigreCommand::DrawTexture:
            {
                SigreCommandDrawTexture* c = (SigreCommandDrawTexture*)command;
                if (!c->texture->isValid())
                    continue;

                tilebackend::Texture texture = c->texture->texture();
                tilebackend::FloatRect target = tilebackend::fromCore(c->target);
                tilebackend::TransformationMatrix matrix = tilebackend::fromCore(transformation * c->modelViewMatrix);

                m_gfx->drawTexture(texture, target, matrix, c->opacity);
#if DBG_DRAW_BORDER
                tilebackend::Color color;
                color.rgba[0] = 1;
                color.rgba[1] = 0;
                color.rgba[2] = 0;
                color.rgba[3] = 0.5;
                m_gfx->drawBorder(target, matrix, color);
#endif
            }
            break;
        case SigreCommand::DrawSolidColor:
            {
                SigreCommandDrawSolidColor* c = (SigreCommandDrawSolidColor*)command;

                tilebackend::FloatRect target = tilebackend::fromCore(c->target);
                tilebackend::TransformationMatrix matrix = tilebackend::fromCore(transformation * c->modelViewMatrix);
                tilebackend::Color color;
                color.rgba[0] = c->color.red() / 255.0f;
                color.rgba[1] = c->color.green() / 255.0f;
                color.rgba[2] = c->color.blue() / 255.0f;
                color.rgba[3] = c->color.alpha() / 255.0f;
                m_gfx->drawSolidColor(target, matrix, color);
            }
            break;
        case SigreCommand::Scissor:
            {
                SigreCommandScissor* c = (SigreCommandScissor*)command;

                tilebackend::FloatRect scissorBox = tilebackend::fromCore(transformation.mapRect(c->scissorBox));
                m_gfx->scissor(scissorBox, c->enable);
            }
            break;
        default:
            ASSERT_NOT_REACHED();
        }
    }
}

#if DEBUG_TILEDBACKEND_V2
bool SigreCommandBuffer::memoryChunkInfoCallback(float pos, float size, bool inuse, void* usrdata)
{
    if (!inuse)
        return true;
    SigreCommandBuffer* that = (SigreCommandBuffer*)usrdata;
    tilebackend::FloatRect target;
    target.x = MemoryBarArea.x() + MemoryBarArea.width() * pos;
    target.y = MemoryBarArea.y();
    target.w = MemoryBarArea.width() * size;
    target.h = MemoryBarArea.height();
    tilebackend::TransformationMatrix matrix = tilebackend::fromCore(TransformationMatrix());
    tilebackend::Color color;
    color.rgba[0] = 0;
    color.rgba[1] = 0.7;
    color.rgba[2] = 0;
    color.rgba[3] = 0.8;
    that->m_gfx->drawSolidColor(target, matrix, color);
    tilebackend::Color color2;
    color2.rgba[0] = 0;
    color2.rgba[1] = 1;
    color2.rgba[2] = 0;
    color2.rgba[3] = 0.5;
    that->m_gfx->drawBorder(target, matrix, color2);
    return true;
}
#endif

}
#endif
